<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>MVVM</title>
    <script>
        /*
        * 1.首先根据上图实现整体的一个架构（包括MVVM类或者VUE类、Watcher类）
        * 这里运用到了观察者模式
        * 2.然后实现MVVM中的由M到V，把模型里面的数据绑定到视图
        * 3.最后实现V-M，当文本框输入文本的时候，由文本事件触发更新模型中的数据
        *
        * */

        //主题
        class Vue{
            constructor(options) {
                this.$data = options.data //获取里面的data值
                this.$el = document.querySelector(options.el) //获取元素对象
                this._watcher = {} //加下划线表示内部私有，容器，用于存放观察者对象
                this.Obsever(this.$data) //当new完实例对象之后就执行里面的代码，给数据
                this.Compile(this.$el) //当new完实例对象之后就执行里面的代码，给元素对象
            }

            //劫持数据
            Obsever(data){ //接收数据
                //容器{}要变成这种格式{myText:[],myBox:[]}
                for(let key in data){ //遍历data，给里面的每一个key下标对应的元素开一个数组容器[]来保存观察者对象
                    //console.log(data)
                    this._watcher[key] = [] //v-text对应保存v-text的数组容器，v-model对应保存v-model的数组容器
                    //console.log(this._watcher)

                    let val = data[key] //获取属性值
                    let watch = this._watcher[key] //获取观察者集合，是一组数组

                    //为每一个属性添加get set拦截
                    Object.defineProperty(this.$data,key,{
                        get:function (){
                            return val
                        },
                        set:function (newVal){
                            if(val !== newVal){
                                val = newVal
                                notify(watch)
                            }
                        }
                    })
                }

                //数据改变了发送通知
                let notify = function (watch){
                    watch.forEach(item => {
                        item.update()
                    })
                }
            }

            //解析指令
            Compile(el){ //接收指令
                //怎么解析指令，就是通过搜索html里面对应的标签名，全部找到它们进行操作
                let nodes = el.children //el是app，它的子类就是h1和div
                for(let i = 0; i < nodes.length; i++){
                    let node = nodes[i] //当i等于0时，就是h1标签，当i等于1时，就是div
                    //但是有个问题，循环只循环了app里面的第一层，只找到了第一层子类h1和div，它的子类div里面还有一层，里面有v-text，v-model没有被找到
                    //所以要在循环里使用递归来查找
                    if(node.children.length){ //如果子类node有长度，说明子类node里面还有子类，需要进去查找
                        this.Compile(node) //那就把子类node传入Compile函数中递归查找
                    }

                    //判断是否存在"v-text"属性
                    if(node.hasAttribute("v-text")){ //hasAttribute方法是查找我们自己定义的属性名
                        //console.log(node.getAttribute("v-text"))
                        let attrVal = node.getAttribute("v-text")
                        //添加观察者
                        this._watcher[attrVal].push(new Watcher(node,this,"innerHTML",attrVal)) //把查找到的v-text添加进数组容器[]
                    }

                    //判断是否存在"v-model"属性
                    if(node.hasAttribute("v-model")){ //hasAttribute方法是查找我们自己定义的属性名
                        //console.log(node.getAttribute("v-model"))
                        let attrVal = node.getAttribute("v-model")
                        //添加观察者
                        this._watcher[attrVal].push(new Watcher(node,this,"value",attrVal)) //把查找到的v-model添加进数组容器[]

                        //监听input输入事件，当有输入的时候就更新this.$data[attrVal]里面的值
                        node.addEventListener("input",(e)=>{
                            //console.log(e.target.value)
                            this.$data[attrVal] = e.target.value //把模型数据更新，意味着接下来就要触发set
                        })
                    }
                }
                console.log(this._watcher)
            }
        }

        //观察者
        class Watcher{
            constructor(el,vue,exp,attrVal) {
                this.el = el
                this.vue = vue
                this.exp = exp
                this.attrVal = attrVal
                this.update() //初始化时更新视图
            }
            update(){
                //obj.innerHTML = vue.data.myBox
                this.el[this.exp] = this.vue.$data[this.attrVal]
            }
        }
    </script>
</head>
<body>
<div id = "app">
    <h1>数据响应式</h1>
    <div>
        <div v-text = "myText"></div>
        <div v-text = "myBox"></div>
        <input type = "text" v-model = "myText">
        <input type = "text" v-model = "myBox">
    </div>
</div>
<script>
    const app = new Vue({
        el:"#app",
        data:{
            myText:"大吉大利",
            myBox:"今晚吃鸡",
        },
    })
</script>
</body>
</html>